{#     Copyright 2020, Kay Hayen, mailto:kay.hayen@gmail.com                    #}
{#                                                                              #}
{#     Part of "Nuitka", an optimizing Python compiler that is compatible and   #}
{#     integrates with CPython, but also works on its own.                      #}
{#                                                                              #}
{#     Licensed under the Apache License, Version 2.0 (the "License");          #}
{#     you may not use this file except in compliance with the License.         #}
{#     You may obtain a copy of the License at                                  #}
{#                                                                              #}
{#        http://www.apache.org/licenses/LICENSE-2.0                            #}
{#                                                                              #}
{#     Unless required by applicable law or agreed to in writing, software      #}
{#     distributed under the License is distributed on an "AS IS" BASIS,        #}
{#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #}
{#     See the License for the specific language governing permissions and      #}
{#     limitations under the License.                                           #}
{#                                                                              #}
static {{target.getTypeDecl()}} SLOT_{{nb_slot}}_{{target.getHelperCodeName()}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}({{left.getVariableDecl("operand1")}}, {{right.getVariableDecl("operand2")}}) {
    {{left.getCheckValueCode("operand1")}}
    {{right.getCheckValueCode("operand2")}}

    double a = PyFloat_AS_DOUBLE(operand1);
    double b = PyFloat_AS_DOUBLE(operand2);

{% if operand in "+-*" %}
    double result = a {{operand}} b;
    {{ target.getReturnFromFloatExpressionCode("result") }}
{% elif operand == "//" %}
    if (unlikely(b == 0)) {
        SET_CURRENT_EXCEPTION_TYPE0_STR(PyExc_ZeroDivisionError, "integer division or modulo by zero");
        return {{target.getExceptionResultIndicatorValue()}};
    }

    if (b == 0.0) {
        SET_CURRENT_EXCEPTION_TYPE0_STR(PyExc_ZeroDivisionError, "float modulo");
        return {{target.getExceptionResultIndicatorValue()}};
    }

    double mod = fmod(a, b);
    double div = (a - mod) / b;

    if (mod) {
        if ((a < 0) != (mod < 0)) {
            div -= 1.0;
        }
    }

    double floordiv;
    if (div) {
        floordiv = floor(div);
        if (div - floordiv > 0.5) {
            floordiv += 1.0;
        }
    } else {
        floordiv = copysign(0.0, a/b);
    }

    {{ target.getReturnFromFloatExpressionCode("floordiv") }}
{% elif operand == "/" %}
    if (b == 0.0) {
        SET_CURRENT_EXCEPTION_TYPE0_STR(PyExc_ZeroDivisionError, "float division by zero");
        return {{target.getExceptionResultIndicatorValue()}};
    }

    double result = a {{operand}} b;
    {{ target.getReturnFromFloatExpressionCode("result") }}
{% elif operand == "%" %}
    if (b == 0.0) {
        SET_CURRENT_EXCEPTION_TYPE0_STR(PyExc_ZeroDivisionError, "float modulo");
        return {{target.getExceptionResultIndicatorValue()}};
    }

    double mod = fmod(a, b);
    if (mod) {
        if ((a < 0) != (mod < 0)) {
            mod += b;
        }
    } else {
        mod = copysign(0.0, b);
    }

    {{ target.getReturnFromFloatExpressionCode("mod") }}
{% elif operand == "divmod" %}
    if (b == 0.0) {
        SET_CURRENT_EXCEPTION_TYPE0_STR(PyExc_ZeroDivisionError, "float modulo");
        return {{target.getExceptionResultIndicatorValue()}};
    }

    double mod = fmod(a, b);
    double div = (a - mod) / b;

    if (mod) {
        if ((a < 0) != (mod < 0)) {
            mod += b;
            div -= 1.0;
        }
    } else {
        mod = copysign(0.0, b);
    }

    double floordiv;
    if (div) {
        floordiv = floor(div);
        if (div - floordiv > 0.5) {
            floordiv += 1.0;
        }
    } else {
        floordiv = copysign(0.0, a/b);
    }

    {{ target.getReturnTupleFromFloatExpressionsCode("floordiv", "mod") }}
{% elif operand == "**" %}
    if (b == 0) {
        {{ target.getReturnFromFloatConstantCode(1.0) }}
    }

    if (Py_IS_NAN(a)) {
{% if target.type_name == "object" %}
    {{ target.getReturnFromObjectExpressionCode("operand1", check_exception=False, take_ref=True) }}
{% else %}
    {{ target.getReturnFromFloatConstantCode("Py_NAN") }}
{% endif %}
    }

    if (Py_IS_NAN(b)) {
        if (a == 1.0) {
            {{ target.getReturnFromFloatConstantCode(1.0) }}
        } else {
{% if target.type_name == "object" %}
            {{ target.getReturnFromObjectExpressionCode("operand2", check_exception=False, take_ref=True) }}
{% else %}
            {{ target.getReturnFromFloatConstantCode("Py_NAN") }}
{% endif %}
        }
    }

    if (Py_IS_INFINITY(b)) {
        a = fabs(a);
        if (a == 1.0) {
            {{ target.getReturnFromFloatConstantCode(1.0) }}
        } else if ((b > 0.0) == (a > 1.0)) {
            {{ target.getReturnFromFloatExpressionCode("fabs(b)") }}
        } else {
            {{ target.getReturnFromFloatConstantCode(0.0) }}
        }
    }

    if (Py_IS_INFINITY(a)) {
        bool b_is_odd = DOUBLE_IS_ODD_INTEGER(b);
        if (b > 0.0) {
            {{ target.getReturnFromFloatExpressionCode("(b_is_odd ? a : fabs(a))") }}
        } else {
            {{ target.getReturnFromFloatExpressionCode("(b_is_odd ? copysign(0.0, a) : 0.0)") }}
        }
    }

    if (a == 0.0) {
        if (b < 0.0) {
            SET_CURRENT_EXCEPTION_TYPE0_STR(PyExc_ZeroDivisionError, "0.0 cannot be raised to a negative power");
            return {{target.getExceptionResultIndicatorValue()}};
        }

        bool b_is_odd = DOUBLE_IS_ODD_INTEGER(b);
        {{ target.getReturnFromFloatExpressionCode("(b_is_odd ? a : 0.0)") }}
    }

    bool negate_result = false;

    if (a < 0.0) {
        if (b != floor(b)) {
            SET_CURRENT_EXCEPTION_TYPE0_STR(PyExc_ValueError, "negative number cannot be raised to a fractional power");
            return {{target.getExceptionResultIndicatorValue()}};
        }
        a = -a;
        negate_result = DOUBLE_IS_ODD_INTEGER(b);
    }

    if (a == 1.0) {
        if (negate_result) {
            {{ target.getReturnFromFloatConstantCode(-1.0) }}
        } else {
            {{ target.getReturnFromFloatConstantCode(1.0) }}
        }
    } else {
        errno = 0;
        const double r = pow(a, b);

        if (unlikely(errno != 0)) {
            PyErr_SetFromErrno(errno == ERANGE ? PyExc_OverflowError : PyExc_ValueError);
            return {{target.getExceptionResultIndicatorValue()}};
        }

        {{ target.getReturnFromFloatExpressionCode("(negate_result ? -r : r)") }}
    }
{% else %}
#error Operand {{operand}} not implemented in {{name}}
{% endif %}
}
